package business;

import entity.Event;
import entity.Weather;
import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import net.aksingh.java.api.owm.DailyForecastData;
import net.aksingh.java.api.owm.OpenWeatherMap;
import org.json.JSONException;

/**
 * The Bean that provides the forecasts for an event and manages forecast
 * updating
 *
 * @author dami
 */
@Stateless
public class WeatherManager {

    @PersistenceContext
    private EntityManager em;

    /**
     * Provides the weather for the day and city of the event passed as
     * parameter. The caller is in charge of attaching the weather to the event.
     *
     * @param e the event you want to enrich with forecast
     * @return the WEATHER entity to attach at the event.
     */
    public Weather createForecastFor(Event e) {
        //Set the starting date of the event in a Calendar type.
        Calendar startDateCal = Calendar.getInstance();
        startDateCal.setTime(e.getStartDate());
        //Set a date to 15 days after the starting date 
        Calendar forecastRangeCal = Calendar.getInstance();
        forecastRangeCal.add(Calendar.DAY_OF_YEAR, 15);
        //Create a default entity weather filling only the sure fields.
        Weather weather = new Weather();
        weather.setCity(e.getCity().toLowerCase());
        weather.setCountry(e.getCountry().toLowerCase());
        weather.setWeatherDate(e.getStartDate());
        weather.setLastUpdate(Calendar.getInstance().getTime());
        weather.setWeatherCode(666666);
        weather.setWeatherDescription("unavailable weather");
        //Control that a forecast may be available
        if (startDateCal.before(forecastRangeCal)) {
            //Control that the weather for the event already exists in the database
            if (checkForecastAlreadyExistent(startDateCal, e.getCity()) != null) {
                weather = checkForecastAlreadyExistent(startDateCal, e.getCity());
                em.persist(weather);
                em.flush();
                return weather;
            } else {
                OpenWeatherMap owm = new OpenWeatherMap(OpenWeatherMap.OWM_URL.PARAMETER_UNITS_VALUE_METRIC, "c63d0290a866387f9003506db7f1b8e9");
                //Number of days we want the forecast to
                byte count = 16;
                //Retrieve a list of forecast for the next 16 days
                try {
                    List<DailyForecastData.Forecast> forecast_list = owm.dailyForecastByCityName(e.getCity(), count).getForecast_List();
                    //If the forecasts have not been retrived it means that the city searched does not match with anyone in the weather API
                    if (forecast_list.isEmpty()) {
                        Logger.getLogger(EventManager.class.getName()).log(Level.INFO, "UNABLE TO RETRIEVE FORECAST FOR CITY: " + e.getCity());
                        em.persist(weather);
                        em.flush();
                        return weather;
                    }
                    //Retrieve the forecast for the starting date of the event.
                    DailyForecastData.Forecast forecast = matchForecast(forecast_list, startDateCal);
                    //Perfectioning of the WEATHER entity 
                    weather.setMaxTemp(forecast.getTemperature_Object().getMaximumTemperature());
                    weather.setMinTemp(forecast.getTemperature_Object().getMinimumTemperature());
                    weather.setWeatherCode(forecast.getWeather_List().get(0).getWeatherCode());
                    weather.setWeatherDescription(forecast.getWeather_List().get(0).getWeatherDescription());
                    //Save the WEATHER in database and return it to the caller
                    em.persist(weather);
                    em.flush();
                    return weather;
                } catch (IOException | JSONException ex) {
                    //Error parsing the preForecast
                    Logger.getLogger(EventManager.class.getName()).log(Level.INFO, "SERVICE TEMPORARY UNAVAILABLE");
                    em.persist(weather);
                    em.flush();
                    return weather;
                }
            }
        }
        //if no forecsat found:
        Logger.getLogger(EventManager.class.getName()).log(Level.INFO, "UNAVAILABLE WEATHER FOR DATE: " + e.getStartDate());
        em.persist(weather);
        em.flush();
        return weather;
    }

    /**
     * The function that, given a date and a list of forecasts, return the
     * forecast for the given date
     *
     * @param fcList the forecasts list
     * @param targetDate the date we want the forecast for
     * @return the selected forecast
     */
    public static DailyForecastData.Forecast matchForecast(List<DailyForecastData.Forecast> fcList, Calendar targetDate) {
        //Set a Calendar object with the date of the forecast.
        Calendar fcDate = Calendar.getInstance();

        for (DailyForecastData.Forecast fc : fcList) {
            fcDate.setTime(fc.getDateTime());
            if ((fcDate.get(Calendar.YEAR) == targetDate.get(Calendar.YEAR))
                    && (fcDate.get(Calendar.DAY_OF_YEAR) == targetDate.get(Calendar.DAY_OF_YEAR))) {
                return fc;
            }
        }
        return null;
    }

    /**
     * the function that, given a target date and a target city, controls if a
     * weather forecast with these parameters already exists in the database,
     * allowing to match it with the event without retrieving a new one.
     *
     * @param targetDate the date you want to control
     * @param targetCity the city you want to control
     * @return the WEATHER found if there is, null if not
     */
    private Weather checkForecastAlreadyExistent(Calendar targetDate, String targetCity) {
        //Create a query that returns all the WEATHERs with the same date and city we're searching for
        TypedQuery<Weather> findPerCityAndDate = em.createQuery("SELECT w FROM Weather w WHERE (w.city = :wcity AND w.weatherDate = :wdate)", Weather.class);
        findPerCityAndDate.setParameter("wcity", targetCity);
        findPerCityAndDate.setParameter("wdate", targetDate.getTime());
        List<Weather> weatherResults = findPerCityAndDate.getResultList();
        if (!weatherResults.isEmpty()) {
            return weatherResults.get(0);
        }
        return null;
    }

}
